/*  Implementation of Unix/Linux tail command.
 * 	Output the last part of files. 
 * 	With more than one FILE, precede each with a header giving the file  name.
 *  With no FILE, or when FILE is -, read standard input.

    Copyright (C) 2012  Vasil Kostov <noobed>
    For any questions or advices don't hesitate to contact me - noobed7@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

#include "tail_TailCommand.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <utility>
#include <signal.h>

using namespace std;

#define SIZE ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN ( 1024 * ( SIZE + 16 ) )

jclass GLOBAL_CLASS;
jobject GLOBAL_OBJECT;
jmethodID METHOD_ID;
JavaVM *jvm;

void sigproc(int signo)
{
 	signal(SIGINT, sigproc);
 	if (signo == SIGINT)
 	{
		//some dynamic data should be deleted here
		//in next versions!
  		JNIEnv *env;
  		jvm->AttachCurrentThread((void **)&env, NULL);
  		env->CallVoidMethod(GLOBAL_OBJECT, METHOD_ID);
  		jvm->DetachCurrentThread();
  		printf("\n");
  		exit(0);
   }
}


JNIEXPORT void JNICALL Java_tail_TailCommand_useLinuxInotifyStruct
  (JNIEnv *env, jobject obj, jobjectArray arr)
{

	env->GetJavaVM(&jvm);
 
	/*define a signal trap! */
	signal(SIGINT, sigproc);

	/*initialize the global variables */ 
	GLOBAL_OBJECT = obj;
	GLOBAL_CLASS = env->GetObjectClass(obj);
	//method id is the same so it's better to cache it
 	//at the beginning
 	jmethodID mid = env->GetMethodID(GLOBAL_CLASS,
     					   "nativeListener", "(Ljava/lang/String;)V");
	METHOD_ID = env->GetMethodID(GLOBAL_CLASS,
   					   "closeEverything", "()V");
 
 	int length, i = 0;
 	int fd; // file descriptor returned by inotify_init();
 	int wd; // watch descriptor - returned when a new event is present
 	char buffer[EVENT_BUF_LEN]; //collect occurences

 	fd = inotify_init();
 
 	/* checking for errors */
 	if(fd < 0)
 	{
  		perror("inotify_init error");
  		exit(20);
 	}
 
 	int stringCount = env->GetArrayLength(arr);

 	//up to file files is supported so far!
 	char files[5][50];
 	int filesIndex[5];
 	//int * filesIndex = new int [stringCount];
 	//char * files[50] = new char [stringCount];



 	for(int i=0; i < stringCount && i < 5; i++) 
 	{
  		jstring string = (jstring) env->GetObjectArrayElement(arr, i);  
  		const char *rawString = env->GetStringUTFChars(string, 0);
  		wd = inotify_add_watch(fd, rawString, IN_MODIFY);
  		if(wd < 0)
  		{
   		perror("inotify_add_watch error");
   		exit(21); 
  		}
  		strcpy(files[i], rawString);
  		filesIndex[i] = wd;
  		env->ReleaseStringUTFChars(string, rawString);
 	}
 

 	while(1)
 	{
  		i = 0;
  		//lenght - On success, the number of bytes read is returned
  		//zero indicates end of file
  		length = read(fd, buffer, EVENT_BUF_LEN);
  
  		/* check if an error occured when reading */
  		if(length < 0)
  		{
   		perror("read error");
   		exit(22);
  		}

  		while(i < length)
  		{
   		struct inotify_event *event = (struct inotify_event*) &buffer[i];
   
   		if(event->mask & IN_MODIFY)
    		for(int i = 0; i < 5; i++)
     		if(filesIndex[i] == event->wd)
     		{
      		//mid refers to nativeListener
      		env->CallVoidMethod(obj, mid, env->NewStringUTF(files[i]));
     		}
     
   		i += sizeof(struct inotify_event) + event->len;
  		}
 	}
}
